from flask import Flask, jsonify, send_from_directory, request
from pathlib import Path
import json
import hashlib
import logging
from uuid_generator import UUIDGenerator
import os
import sys
import time
from watchdog.observers import Observer
from watchdog.events import FileSystemEventHandler
from rich.logging import RichHandler
from rich.console import Console

# 应用配置
app = Flask(__name__)
app.config.update({
    'DOC_DIR': Path('./docs').resolve(),
    'LIST_FILE': Path('list.json'),
    'ENCODING': 'utf-8',
    'MAX_FILE_SIZE': 10 * 1024 * 1024,  # 10MB
    'WATCHDOG_ENABLED': True,
    'DEBOUNCE_INTERVAL': 1.5  # 防抖间隔（秒）
})

# 日志配置
logging.basicConfig(
    level=logging.INFO,
    format="%(message)s",
    datefmt="[%X]",
    handlers=[RichHandler(rich_tracebacks=True)]
)
logger = logging.getLogger("WebMD")
console = Console()

class FileChangeHandler(FileSystemEventHandler):
    def __init__(self, doc_manager):
        self.doc_manager = doc_manager
        self.last_trigger = 0

    def on_any_event(self, event):
        if not app.config['WATCHDOG_ENABLED']:
            return
        
        current_time = time.time()
        if current_time - self.last_trigger > app.config['DEBOUNCE_INTERVAL']:
            logger.info(f"检测到文件变更: {event.event_type} {event.src_path}")
            try:
                self.doc_manager.refresh_map()
                logger.info("文档映射已更新")
            except Exception as e:
                logger.error(f"更新失败: {str(e)}")
            finally:
                self.last_trigger = current_time

class DocumentManager:
    def __init__(self):
        self.ulid_map = {}
        self.structure = []
        self._current_paths = set()
        self.observer = None
        self._init_storage()
        self.start_watcher()
        self.load_or_generate_map()

    def _init_storage(self):
        """初始化存储结构"""
        try:
            app.config['DOC_DIR'].mkdir(parents=True, exist_ok=True)
            app.config['LIST_FILE'].touch(exist_ok=True)
        except Exception as e:
            logger.critical(f"存储初始化失败: {str(e)}")
            sys.exit(1)

    def start_watcher(self):
        """启动文件监控"""
        if app.config['WATCHDOG_ENABLED']:
            self.observer = Observer()
            handler = FileChangeHandler(self)
            self.observer.schedule(
                handler,
                path=str(app.config['DOC_DIR']),
                recursive=True
            )
            self.observer.start()
            logger.info("文件监控已启动")

    def stop_watcher(self):
        """停止文件监控"""
        if self.observer:
            self.observer.stop()
            self.observer.join()
            logger.info("文件监控已停止")

    def refresh_map(self):
        """增量更新映射"""
        self._current_paths.clear()
        new_structure = self.scan_directory(app.config['DOC_DIR'])
        self._merge_structure(new_structure)
        self._cleanup_removed_items()
        self.save_map()

    def _merge_structure(self, new_structure):
        """合并新旧结构"""
        # 实现结构合并逻辑（可根据需求优化）
        self.structure = new_structure

    def scan_directory(self, path: Path, parent_id: str = None) -> list:
        """递归扫描目录"""
        structure = []
        try:
            for item in path.iterdir():
                if item.name.startswith('.') or not item.exists():
                    continue

                # 生成路径标识
                rel_path = str(item.relative_to(app.config['DOC_DIR']))
                path_hash = hashlib.sha256(rel_path.encode()).hexdigest()
                self._current_paths.add(path_hash)

                # 计算哈希
                if item.is_file():
                    current_hash = self._file_hash(item)
                else:
                    current_hash = self._dir_hash(item)

                # 更新或生成UUID
                if path_hash in self.ulid_map:
                    stored_hash, ulid = self.ulid_map[path_hash]
                    if stored_hash != current_hash:
                        ulid = UUIDGenerator.generate()
                else:
                    ulid = UUIDGenerator.generate()

                self.ulid_map[path_hash] = (current_hash, ulid)

                # 构建节点
                node = {
                    'id': ulid,
                    'name': item.name,
                    'type': 'dir' if item.is_dir() else 'file',
                    'path': rel_path,
                    'parent': parent_id,
                    'children': []
                }

                if item.is_dir():
                    node['children'] = self.scan_directory(item, ulid)

                structure.append(node)
        except Exception as e:
            logger.error(f"扫描失败: {str(e)}")
        return structure

    def _file_hash(self, path: Path) -> str:
        """计算文件哈希"""
        try:
            content = path.read_bytes() if path.stat().st_size < app.config['MAX_FILE_SIZE'] else b''
            return hashlib.sha256(content).hexdigest()
        except Exception as e:
            logger.warning(f"文件哈希计算失败: {path} - {str(e)}")
            return ""

    def _dir_hash(self, path: Path) -> str:
        """计算目录哈希"""
        hashes = []
        try:
            for child in path.iterdir():
                if child.is_file():
                    hashes.append(self._file_hash(child))
                else:
                    hashes.append(self._dir_hash(child))
        except Exception as e:
            logger.warning(f"目录哈希计算失败: {path} - {str(e)}")
        return hashlib.sha256(",".join(hashes).encode()).hexdigest()

    def load_or_generate_map(self):
        """加载或生成映射"""
        try:
            if app.config['LIST_FILE'].stat().st_size > 0:
                with open(app.config['LIST_FILE'], 'r', encoding=app.config['ENCODING']) as f:
                    self.ulid_map = {k: tuple(v) for k, v in json.load(f).items()}
        except Exception as e:
            logger.warning(f"列表文件加载失败: {str(e)}")

        self.refresh_map()

    def _cleanup_removed_items(self):
        """清理已删除项"""
        removed = set(self.ulid_map.keys()) - self._current_paths
        for path_hash in removed:
            del self.ulid_map[path_hash]
            logger.info(f"清理已删除项: {path_hash}")

    def save_map(self):
        """保存映射"""
        with open(app.config['LIST_FILE'], 'w', encoding=app.config['ENCODING']) as f:
            json.dump({k: list(v) for k, v in self.ulid_map.items()}, f, indent=2)
        logger.info(f"已保存 {len(self.ulid_map)} 项映射")

    def find_item(self, ulid: str) -> dict:
        """查找文档项"""
        def _search(nodes):
            for node in nodes:
                if node['id'] == ulid:
                    return node
                if found := _search(node.get('children', [])):
                    return found
            return None
        return _search(self.structure)

# 初始化文档管理
doc_manager = DocumentManager()

@app.route('/api/structure')
def get_structure():
    parent = request.args.get('parent')
    if parent:
        parent_node = doc_manager.find_item(parent)
        return jsonify(parent_node.get('children', [])) if parent_node else jsonify([])
    return jsonify(doc_manager.structure)

@app.route('/api/doc/<ulid>')
def get_document(ulid):
    try:
        target = doc_manager.find_item(ulid)
        if not target or target['type'] != 'file':
            return jsonify({'error': 'Document not found'}), 404

        file_path = app.config['DOC_DIR'] / target['path']
        return jsonify({
            'content': file_path.read_text(encoding=app.config['ENCODING']),
            'meta': target
        })
    except Exception as e:
        logger.error(f"文档加载失败: {str(e)}")
        return jsonify({'error': 'Internal server error'}), 500

"""
@app.route('/')
def index():
    return send_from_directory('templates', 'index.html')
"""

@app.route('/')
def index():
    doc_id = request.args.get('id')
    
    if doc_id:
        target = doc_manager.find_item(doc_id)
        if target and target['type'] == 'file':
            return send_from_directory('templates', 'index.html')
    
    return send_from_directory('templates', 'index.html')


@app.route('/static/<path:path>')
def send_static(path):
    return send_from_directory('static', path)

@app.errorhandler(404)
def page_not_found(e):
    return send_from_directory('templates', '404.html'), 404

@app.errorhandler(403)
def forbidden(e):
    return send_from_directory('templates', '403.html'), 403


if __name__ == '__main__':
    try:
        app.run(host='0.0.0.0', port=5000, debug=False)
    except KeyboardInterrupt:
        doc_manager.stop_watcher()
    finally:
        doc_manager.stop_watcher()